/**
 * 
 */
package dk.dtu.is.controllers;

import java.io.StringReader;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import dk.dtu.is.model.iec61850.MeasuredValue;
import dk.dtu.is.model.iec61850.ln.Zcev;

/**
 * @author dsac
 * 
 */
public class NodeParser {

	private String node;

	/**
	 * Constructor
	 */
	public NodeParser(String node) {
		this.node = node;
	}

	/**
	 * It checks whether the current node contains the @logicalNodeName
	 * 
	 * @param logicalNodeName
	 * @return
	 */
	public boolean hasThisLogicalNode(String logicalNodeName) {
		// TODO
		return true;
	}

	/**
	 * Parse the node and get the ZCEV object back.
	 * 
	 * @return
	 */
	public Zcev getZcev() {
		Zcev zcev = null;

		if (node == null) {
			return null;
		}

		if (!(hasThisLogicalNode("ZCEV"))) {
			return null;
		}

		// Parse the data, filtering out the start elements
		XMLInputFactory xmlif = XMLInputFactory.newInstance();
		StringReader sr = new StringReader(node);
		XMLStreamReader reader = null;

		try {
			reader = xmlif.createXMLStreamReader(sr);
			readRoot(reader); // skip first useless elements
			zcev = readZcevLogicalNode(reader);
			// System.out.println("ZCEV parsed: " + zcev.toString());
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return zcev;
	}

	/**
	 * Simple method to move the cursor to the beginning of the LogicalNode
	 * 
	 * @param reader
	 * @throws XMLStreamException
	 */
	private void readRoot(XMLStreamReader reader) throws XMLStreamException {
		// skip all the possible initial elements
		reader.nextTag();
	}

	/**
	 * Read abd return a ZCEV as defined in the extension of the IEC 61850
	 * standard.
	 * 
	 * @param reader
	 * @return
	 * @throws XMLStreamException
	 */
	private Zcev readZcevLogicalNode(XMLStreamReader reader)
			throws XMLStreamException {
		Zcev zcev = new Zcev();

		QName name;
		String type;

		// Test that the first part is a Logical Node of type ZCEV
		name = reader.getName();
		boolean isLogicalNode = "LN".equals(name.getLocalPart());
		if (!(isLogicalNode))
			throw new XMLStreamException(
					"readZcevLogicalNode is supposed to read a LogicalNode. Found: "
							+ name);

		// check if it is a MeasurementValue
		type = reader.getAttributeValue(null, "Type");
		boolean isZcev = "ZCEV".equals(type);
		if (!(isZcev))
			throw new XMLStreamException(
					"readZcevLogicalNode is supposed to read a ZCEV. Found: "
							+ type);

		// read the first DataObject on the ZCEV
		// this comes immediately after the LogicalNode element
		reader.nextTag();

		// I keep iterating the logical node until I get to the end of the
		// logical node element.
		// this is defined as a END_ELEMENT whose name is "LN".
		// NB: a logical node can not contain any other logical node
		while (!(reader.getName().getLocalPart().equals("LN"))) {
			// Test that we have a data attribute inside
			name = reader.getName();
			boolean isDataObject = "DO".equals(name.getLocalPart());
			if (!(isDataObject))
				throw new XMLStreamException(
						"readZcevLogicalNode is supposed to contain only DataObject (DO). Found: "
								+ name);

			// start the real parsing
			type = reader.getAttributeValue(null, "Type");
			String nameAttribute = reader.getAttributeValue(null, "Name");

			if (type.equals("MV")) {
				MeasuredValue mv;
				if (nameAttribute.equals("SOC")) {
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setSoc(mv.getAnalogueValue());
				} else if (nameAttribute.equals("Vol")) {
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setVol(mv.getAnalogueValue());
				} else if (nameAttribute.equals("Amp")) {
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setCur(mv.getAnalogueValue());
				} else if (nameAttribute.equals("CellVols")) {
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setCellVols(mv.getAnalogueValues());
				} else {
					skipDataObject(reader);
				}
			}
			// case "ASG":
			// ...
			// break
			// ...
			else {
				// if what I am reading is not in the case list above, I skip
				// all the internal children (which are DataObject)
				skipDataObject(reader);
			} // case
		} // while

		return zcev;
	}

	/**
	 * It reads and returns a MeasurementValue as defined in IEC 61850 standard
	 * 
	 * @param reader
	 * @return
	 * @throws XMLStreamException
	 */
	private MeasuredValue readMeasurementValueDataObject(XMLStreamReader reader)
			throws XMLStreamException {
		MeasuredValue mv = new MeasuredValue();

		QName name;
		String type;

		// check if it is a DataObject
		name = reader.getName();
		boolean isDataObject = "DO".equals(name.getLocalPart());
		if (!(isDataObject))
			throw new XMLStreamException(
					"readMeasurementValueDataObject is supposed to read a DataObject. Found: "
							+ name);

		// check if it is a MeasurementValue
		type = reader.getAttributeValue(null, "Type");
		boolean isMeasurementValue = "MV".equals(type);
		if (!(isMeasurementValue))
			throw new XMLStreamException(
					"readMeasurementValueDataObject is supposed to read a DataObject whose Type is \"MV\". Found: "
							+ type);

		// go one level lower: start processing its DataAttributes
		while (reader.nextTag() == XMLEvent.START_ELEMENT) {
			// Test that we have a data attribute inside
			name = reader.getName();
			boolean isDataAttribute = "DA".equals(name.getLocalPart());
			if (!(isDataAttribute))
				throw new XMLStreamException(
						"readMeasurementValueDataObject is supposed to contain only DataAttributes. Found: "
								+ name);

			type = reader.getAttributeValue(null, "Type");
			if (type.equals("AnalogueValue")) {
				// access the START ELEMENT which contains the value, that is,
				// Type=FLOAT32
				reader.nextTag();
				if (reader.getAttributeValue(null, "IsArray") != null) {
					mv.setAnalogueValues(this.getAnalogueValues(reader));
					mv.setArray(true);
					reader.nextTag();
				} else {
					mv.setAnalogueValue(this.getAnalogueValue(reader));
				}
			}
			// case "Quality":
			// <mv.setQuality>
			// break
			// ...
			else {
				skipDataAttributes(reader);
			} // case

		} // while

		// skip the END_ELEMENT of the current DataObject which contained the
		// Measurement Value
		reader.next();

		return mv;
	}

	/**
	 * Common Data Attribute Types: Analogue Value 61850-7-3, Chapter 6.3,
	 * 
	 * Analogue values may be represented as a basic data type INTEGER
	 * (attribute i) or as FLOATING POINT (attribute f). At least one of the
	 * attributes shall be used.
	 * 
	 * As an example, if xxx.f is written, the application shall update xxx.i
	 * accordingly.
	 * 
	 */
	private float getAnalogueValue(XMLStreamReader reader)
			throws XMLStreamException {
		Float value = null;

		// check if it is a DA
		QName name = reader.getName();
		boolean isDataAttribute = "DA".equals(name.getLocalPart());
		if (!(isDataAttribute))
			throw new XMLStreamException(
					"getAnalogueValue is supposed to read only DataAttributes. Found: "
							+ name);

		// extract the integer or float value
		boolean isFloat = "f".equals(reader.getAttributeValue(null, "Name"));
		boolean isInteger = "i".equals(reader.getAttributeValue(null, "Name"));
		if (isFloat || isInteger) {
			String elementText = reader.getElementText();
			value = new Float(Float.parseFloat(elementText));
		}

		// skip the END ELEMENT Tag
		reader.next();

		return value.floatValue();
	}

	/**
	 * Gets the analogue values.
	 * 
	 * @param reader
	 *            the reader
	 * @return the analogue values
	 */
	private float[] getAnalogueValues(XMLStreamReader reader)
			throws XMLStreamException {
		// TODO follow the same structure of the "getAnalogueValue" method and
		// extract the single cell voltages.
		// float values[] = null;
		//TODO: preparse 
		float[] values = new float[110];

		// check if it is a DA
		QName name = reader.getName();
		boolean isDataAttribute = "DA".equals(name.getLocalPart());
		if (!(isDataAttribute))
			throw new XMLStreamException(
					"getAnalogueValue is supposed to read only DataAttributes. Found: "
							+ name);

		// extract the integer or float value
		boolean isFloat = "f".equals(reader.getAttributeValue(null, "Name"));
		boolean isInteger = "i".equals(reader.getAttributeValue(null, "Name"));
		for (int i = 0; i < values.length; i++) {
			reader.nextTag();
			String elementText = reader.getElementText();
			values[i] = Float.parseFloat(elementText);
//			System.out.println(values[i]);
		}
		// skip the END ELEMENT Tag
		reader.next();

		return values;
	}

	/**
	 * It skips an entire dataObject when the cursor is on it. It moves the
	 * cursor forward to the next DataObject.
	 * 
	 * @param reader
	 * @throws XMLStreamException
	 */
	private void skipDataObject(XMLStreamReader reader)
			throws XMLStreamException {
		// System.out.println("Skipping DO, Type: \"" +
		// reader.getAttributeValue("", "Type") + "\", Name: \"" +
		// reader.getAttributeValue("", "Name") + "\"");

		// skip all the internal children (which are DataObject)

		boolean dataObjectEndElementFound = false;
		while (!dataObjectEndElementFound) {
			int eventType = reader.next();

			// continue to read the next event until I get to a START ELEMENT
			if (eventType == XMLEvent.START_ELEMENT) {
				// System.out.println("  Skipping the DA: \"" +
				// reader.getAttributeValue("", "Name") + "\"");
			}

			if (eventType == XMLEvent.END_ELEMENT)
				if (reader.getName().getLocalPart().equals("DO")) {
					dataObjectEndElementFound = true;
					// System.out.println("End DO\n");
				}
		}
		// move to the next DataObject
		reader.nextTag();
	}

	/**
	 * It skips an entire dataAttibute when the cursor is on it. It moves the
	 * cursor forward to the next DataAttribute.
	 * 
	 * If this DataAttributes has children (other DataAttributes), it also skips
	 * them
	 * 
	 * @param reader
	 * @param currentOpenElement
	 * @throws XMLStreamException
	 */
	private void skipDataAttributes(XMLStreamReader reader)
			throws XMLStreamException {
		int openElementsCount = 1;
		int eventType;
		while (openElementsCount != 0) {
			eventType = reader.next();

			if (eventType == XMLEvent.START_ELEMENT)
				openElementsCount++;
			if (eventType == XMLEvent.END_ELEMENT)
				openElementsCount--;
		}
	}

	/**
	 * Utility class. It returns the string name of the Event Type
	 * 
	 * @param eventType
	 * @return
	 */
	public static final String getEventTypeString(int eventType) {
		switch (eventType) {
		case XMLEvent.START_ELEMENT:
			return "START_ELEMENT";

		case XMLEvent.END_ELEMENT:
			return "END_ELEMENT";

		case XMLEvent.PROCESSING_INSTRUCTION:
			return "PROCESSING_INSTRUCTION";

		case XMLEvent.CHARACTERS:
			return "CHARACTERS";

		case XMLEvent.COMMENT:
			return "COMMENT";

		case XMLEvent.START_DOCUMENT:
			return "START_DOCUMENT";

		case XMLEvent.END_DOCUMENT:
			return "END_DOCUMENT";

		case XMLEvent.ENTITY_REFERENCE:
			return "ENTITY_REFERENCE";

		case XMLEvent.ATTRIBUTE:
			return "ATTRIBUTE";

		case XMLEvent.DTD:
			return "DTD";

		case XMLEvent.CDATA:
			return "CDATA";

		case XMLEvent.SPACE:
			return "SPACE";
		}

		return "";
	}

}
